/*
	Copyright (c) 2007, ANYwebcam.com Pty Ltd. All rights reserved.

	The software in this package is published under the terms of the BSD style 
	license, a copy of which has been included with this distribution in the 
	license.txt file.
*/
package com.anywebcam.di
{
	import com.anywebcam.di.*;
	import com.anywebcam.di.component.*;
	import com.anywebcam.di.error.*;
	import com.anywebcam.di.testsupport.*;
	import com.anywebcam.reflection.*;

	import flexunit.framework.TestCase;
	import flexunit.framework.TestSuite;

	public class DefaultDIContainerTest extends TestCase
	{
		public static function suite():TestSuite
		{
			var ts:TestSuite = new TestSuite();

			var tests:Array = 
			[
				// test options
				'shouldProvideDefaultOptionsIfNoneSetInConstructor',
				'shouldAcceptOptionsInConstructor',
				'shouldSetOptionsViaOptionsSetter',
				
				// test registerX()
				'shouldHaveComponentAfterRegisterComponent',
				'shouldHaveComponentAfterRegisterInstance',
				'shouldHaveComponentAfterRegisterType',

				// test register() variations
				'registerShouldRegisterWithClass',
				'registerShouldRegisterWithInterfaceClass',
				'registerShouldRegisterWithClassClass',
				'registerShouldRegisterWithStringKeyClass',
				'registerShouldRegisterWithInterfaceClassParameters',
				'registerShouldRegisterWithClassClassParameters',
				'registerShouldRegisterWithStringKeyClassParameters',				
					
				// test getInstanceX()
				'shouldComplainOnGetInstanceIfNoComponentForStringKey',
				'shouldComplainOnGetInstanceIfNoComponentImplementsClassKey',
				'shouldComplainOnGetInstanceIfComponentHasCyclicDependency'
			];

			tests.forEach( function( methodName:String, index:int, array:Array ):void
			{
				ts.addTest( new DefaultDIContainerTest( methodName ) );
			});

			return ts;
		}

		public function DefaultDIContainerTest( method:String )
		{
			super( method );
		}
		
		public function shouldProvideDefaultOptionsIfNoneSetInConstructor():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer();
			
			assertNotNull( 'Expecting DefaultDIContainer to have default options', container.options );
			assertNotNull( 'Expecting DefaultDIContainer options to have injectionType value', container.options.injectionType );
			assertNotNull( 'Expecting DefaultDIContainer options to have cache value', container.options.cache );
		}
		
		public function shouldAcceptOptionsInConstructor():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:false } );
			
			assertNotNull( 'Expecting DefaultDIContainer to have options', container.options );
			assertNotNull( 'Expecting DefaultDIContainer options to have injectionType value', container.options.injectionType );
			assertNotNull( 'Expecting DefaultDIContainer options to have cache value', container.options.cache );
			assertFalse( 'Expecting DefaultDIContainer options to have cache value of false', container.options.cache );
		}
		
		public function shouldSetOptionsViaOptionsSetter():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer();
			container.options = { cache:false };
			
			assertNotNull( 'Expecting DefaultDIContainer to have options', container.options );
			assertNotNull( 'Expecting DefaultDIContainer options to have injectionType value', container.options.injectionType );
			assertNotNull( 'Expecting DefaultDIContainer options to have cache value', container.options.cache );
			assertFalse( 'Expecting DefaultDIContainer options to have cache value of false', container.options.cache );
		}
		
		public function shouldHaveComponentAfterRegisterComponent():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer();
			var component:DIComponent = new InstanceComponent( DIContainer, container );
			
			container.register( component );
			
			assertNotNull( 'Expecting DefaultDIContainer to have components', container.components );
			assertTrue( 'Expecting DefaultDIContainer to have more then 0 components', container.components.length == 1 );
		}
		
		public function shouldHaveComponentAfterRegisterInstance():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer();
			
			container.registerInstance( DIContainer, container );
			
			assertNotNull( 'Expecting DefaultDIContainer to have components', container.components );
			assertTrue( 'Expecting DefaultDIContainer to have more then 0 components', container.components.length == 1 );
		}
		
		public function shouldHaveComponentAfterRegisterType():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer();
			
			container.registerType( 'caching-component', CachingComponent );
			
			assertNotNull( 'Expecting DefaultDIContainer to have components', container.components );
			assertTrue( 'Expecting DefaultDIContainer to have more then 0 components', container.components.length == 1 );
		}
		
		public function shouldHaveComponentWhichMatchesOptionsAfterRegisterImplementation():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache: false } );
			
			container.options.injectionType = 'setter';
			var setterComponent:DIComponent = container.registerType( 'component-one', ClassForTestingOnly );
			assertTrue( 'Expecting registered component to be same as the Class for the injectionType', 
						setterComponent is (container.injectionTypeToDIComponentMap[ 'setter' ] as Class) );
			
			container.options.injectionType = 'constructor';
			var constructorComponent:DIComponent = container.registerType( 'component-two', ClassForTestingOnly );
			assertTrue( 'Expecting registered component to be same as the Class for the injectionType', 
						constructorComponent is (container.injectionTypeToDIComponentMap[ 'constructor' ] as Class) );
		}
		
		public function shouldComplainOnGetInstanceIfNoComponentForStringKey():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer();
			
			try
			{
				container.getInstance( 'string-key-test' );
				
				fail( 'Expecting DefaultDIContainer to complain about not have a component for the String key' );
			}
			catch( e:DIError )
			{	
			}
		}
		
		public function shouldComplainOnGetInstanceIfNoComponentImplementsClassKey():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer();
			
			try
			{
				container.getInstance( ClassForTestingOnly );
				
				fail( 'Expecting DefaultDIContainer to complain about not having a component for the Class key' );
			}
			catch( e:DIError )
			{	
			}
		}
		
		public function shouldComplainOnGetInstanceIfComponentHasCyclicDependency():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:true, injectionType:'constructor' } );
			container.registerType( SelfDependentClass, SelfDependentClass );
			container.registerType( CyclicDependentOne, CyclicDependentOne );
			container.registerType( CyclicDependentTwo, CyclicDependentTwo );
			
			try
			{
				container.getInstance( SelfDependentClass );
				
				fail( 'Expecting DefaultDIContainer to throw a CyclicDependencyError for type:' + SelfDependentClass );				
			}
			catch( e:CyclicDependencyError )
			{
			}			
			
			try
			{
				container.getInstance( CyclicDependentOne );
				
				fail( 'Expecting DefaultDIContainer to throw a CyclicDependencyError for type:' + CyclicDependentOne );				
			}
			catch( e:CyclicDependencyError )
			{
			}
		}
		
		// test register() variations
		public function registerShouldRegisterWithClass():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:true, injectionType:'constructor' } );
			var component:DIComponent = container.register( ClassForTestingOnly );
			assertNotNull( 'Expecting component to be returned for register', component );
			assertStrictlyEquals( 'Expecting component.type to match registered type', component.type, ClassForTestingOnly );
		}
		
		public function registerShouldRegisterWithInterfaceClass():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:true, injectionType:'constructor' } );
			var component:DIComponent = container.register( InterfaceForTestingOnly, ClassForTestingOnly );
			assertNotNull( 'Expecting component to be returned for register', component );
			assertStrictlyEquals( 'Expecting component.key to match registered key', component.key, InterfaceForTestingOnly );
			assertStrictlyEquals( 'Expecting component.type to match registered type', component.type, ClassForTestingOnly );
		}
		
		public function registerShouldRegisterWithClassClass():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:true, injectionType:'constructor' } );
			var component:DIComponent = container.register( ClassForTestingOnly, ClassForTestingOnly );
			assertNotNull( 'Expecting component to be returned for register', component );
			assertStrictlyEquals( 'Expecting component.key to match registered key', component.key, ClassForTestingOnly );
			assertStrictlyEquals( 'Expecting component.type to match registered type', component.type, ClassForTestingOnly );
		}
		
		public function registerShouldRegisterWithStringKeyClass():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:true, injectionType:'constructor' } );
			var component:DIComponent = container.register( 'testing-only', ClassForTestingOnly );
			assertNotNull( 'Expecting component to be returned for register', component );
			assertStrictlyEquals( 'Expecting component.key to match registered key', component.key, 'testing-only' );
			assertStrictlyEquals( 'Expecting component.type to match registered type', component.type, ClassForTestingOnly );
		}
		
		public function registerShouldRegisterWithInterfaceClassParameters():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:true, injectionType:'constructor' } );
			var component:DIComponent = container.register( InterfaceForTestingOnly, ClassForTestingOnly, [1,2,3] );
			assertNotNull( 'Expecting component to be returned for register', component );
			assertStrictlyEquals( 'Expecting component.key to match registered key', component.key, InterfaceForTestingOnly );
			assertStrictlyEquals( 'Expecting component.type to match registered type', component.type, ClassForTestingOnly );
		}
		
		public function registerShouldRegisterWithClassClassParameters():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:true, injectionType:'constructor' } );
			var component:DIComponent = container.register( ClassForTestingOnly, ClassForTestingOnly, [1,2,3] );
			assertNotNull( 'Expecting component to be returned for register', component );
			assertStrictlyEquals( 'Expecting component.key to match registered key', component.key, ClassForTestingOnly );
			assertStrictlyEquals( 'Expecting component.type to match registered type', component.type, ClassForTestingOnly );
		}
		
		public function registerShouldRegisterWithStringKeyClassParameters():void
		{
			var container:DefaultDIContainer = new DefaultDIContainer( { cache:true, injectionType:'constructor' } );
			var component:DIComponent = container.register( 'testing-only', ClassForTestingOnly, [1,2,3] );
			assertNotNull( 'Expecting component to be returned for register', component );
			assertStrictlyEquals( 'Expecting component.key to match registered key', component.key, 'testing-only' );
			assertStrictlyEquals( 'Expecting component.type to match registered type', component.type, ClassForTestingOnly );
		}
	}
}
